﻿using System;
using System.Collections.Generic;
using System.Threading;
using SpLocalizer.Common;
using SpLocalizer.Common.Patterns;
using Core.Threading.ThreadPools.Statistics;
using Core.Threading.ThreadPools.TaskItems;
using Core.Threading.ThreadPools.TaskQueueControllers;
using Microsoft.Practices.EnterpriseLibrary.Logging;
using SpLocalizer.Common.Extensions;
using Microsoft.Practices.Unity;

namespace Core.Threading.ThreadPools
{
    public sealed class ExtendedThreadPool : IDisposable
    {
        private static readonly Singleton<ExtendedThreadPool> _extendedThreadPool =
            new Singleton<ExtendedThreadPool>(ServiceContainer.Resolve<ExtendedThreadPool>);

        private readonly object _locker = new object();

        private readonly LogWriter _log = ServiceContainer.Resolve<LogWriter>();
        private readonly IStatisticController _statisticController;
        private readonly List<WorkThread> _workThreads = new List<WorkThread>();
        private int _maxThreads;
        private int _minThreads;

        [InjectionConstructor]
        public ExtendedThreadPool()
            : this(1, 1)//25
        {
        }

        /// <summary>
        /// Create new Instance of ExtendedThreadPool
        /// </summary>
        /// <param name="minThreads">MinThreads per processor</param>
        /// <param name="maxThreads">MaxThreads per processor</param>
        public ExtendedThreadPool(int minThreads, int maxThreads)
            : this(minThreads, maxThreads, MultiThreadingCapacityType.PerProcessor)
        {
        }

        public ExtendedThreadPool(int minThreads, int maxThreads, MultiThreadingCapacityType multiThreadingCapacityType)
        {
            SetThreadingRange(minThreads, maxThreads, multiThreadingCapacityType);
            _statisticController = new StatisticController(_maxThreads);
        }

        #region IDisposable Members

        public void Dispose()
        {
            Stop();
        }

        #endregion

        private void SetThreadingRange(int minThreads, int maxThreads,
                                       MultiThreadingCapacityType multiThreadingCapacityType)
        {
            ValidateThreadingRange(minThreads, maxThreads);
            switch (multiThreadingCapacityType)
            {
                case MultiThreadingCapacityType.Global:
                    _minThreads = minThreads;
                    _maxThreads = maxThreads;
                    break;
                case MultiThreadingCapacityType.PerProcessor:
                    int processorCount = Environment.ProcessorCount;
                    _minThreads = processorCount*minThreads;
                    _maxThreads = processorCount*maxThreads;
                    break;
                default:
                    throw new ArgumentOutOfRangeException("multiThreadingCapacityType",
                                                          string.Format(
                                                              "Invalid MultiThreadingCapacityType value: {0}",
                                                              multiThreadingCapacityType));
            }
        }

        /// <summary>
        /// Stops all the working threads and waits until they are actually ended
        /// </summary>
        public void Stop()
        {
            if (_workThreads.IsNull())
                return;

            lock (_locker)
            {
                foreach (WorkThread workThread in _workThreads)
                {
                    if (workThread.IsNotNull())
                        workThread.Dispose();
                        workThread.SafeDispose(ex => _log.Write(ex,Constants.ExceptionLogCategory));
                }
            }
        }

        public void AddTask(Action action, TaskItemPriority priority)
        {
            AddWorkItem(new WorkItem(new ActionTaskItem(action, priority)));
        }

        /// <summary>
        /// Add taskItem with default <see cref="TaskItemPriority.Normal"/>
        /// </summary>
        /// <param name="action"></param>
        public void AddTask(Action action)
        {
            AddWorkItem(new WorkItem(new ActionTaskItem(action)));
        }

        /// <summary>
        /// Add taskItem with default <see cref="TaskItemPriority.Normal"/>
        /// </summary>
        /// <param name="taskItem"></param>
        public void AddTask(ITaskItem taskItem)
        {
            AddWorkItem(new WorkItem(taskItem));
        }

        public void AddTask(ITaskItem taskItem, TaskItemPriority priority)
        {
            AddWorkItem(new WorkItem(taskItem, priority));
        }

        public ExtendedThreadPoolStats GetStatistic()
        {
            return _statisticController.GetStatistic(TaskQueueController.ConsumersWaiting);
        }

        private void AddWorkItem(WorkItem workItem)
        {
            _statisticController.WorkItemQueued();
            TaskQueueController.Enqueue(workItem);
            CreateWorkThread();
        }

        /// <summary>
        /// Return true when new <see cref="WorkThread"/> should be created
        /// </summary>
        /// <returns></returns>
        /// <remarks>When count of worker = MaxThread we should not wait on ConsumersWaiting lock</remarks>
        private bool IsStartNewWorker()
        {
            lock (_locker)
            {
                if (_workThreads.Count < MaxThreads)
                    return TaskQueueController.ConsumersWaiting == 0;
                return false;
            }
        }

        private void ValidateThreadingRange(int minThreads, int maxThreads)
        {
            if (minThreads > maxThreads)
                throw new ArgumentException(string.Format("MinThreads {0} should be less MaxThreads {1}",
                                                          minThreads, maxThreads));
            if (minThreads <= 0)
                throw new ArgumentException(string.Format("MinThreads {0} should not be greater zero", minThreads));
        }

        private void CreateWorkThread()
        {
            lock (_locker)
            {
                if (!IsStartNewWorker())
                    return;
                var workThread = new WorkThread(TaskQueueController, _statisticController);
                _workThreads.Add(workThread);
                var thread = new Thread(workThread.Start)
                                 {
                                     Name = string.Format("WorkThread {0}", _workThreads.Count),
                                     IsBackground = true,
                                 };
                workThread.Thread = thread;
                thread.Start();
            }
        }

        #region Property

        /// <summary>
        /// Create default instance of ExtendedThreadPool
        /// MinThreads = 1 per processor
        /// MaxThreads =25 per processor
        /// </summary>
        public static ExtendedThreadPool Default
        {
            get { return _extendedThreadPool.Value; }
        }

        [Dependency]
        public ITaskQueueController TaskQueueController { private get; set; }

        public int MinThreads
        {
            get { return _minThreads; }
        }

        public int MaxThreads
        {
            get { return _maxThreads; }
        }

        #endregion Property
    }
}